using System;
using CodeConsult.Nordic.nRF8001.ApplicationControlInterface;
using CodeConsult.Nordic.nRF8001.ApplicationControlInterface.Commands;
using CodeConsult.Nordic.nRF8001.ApplicationControlInterface.Events;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace CodeConsult.Nordic.nRF8001
{
    public class Aci
    {
        private SPI _spiPort;
        private SPI.Configuration _spiConfiguration;
        private BitOrder _mcuSpiBitOrder;
        private const bool SpiChipSelectActiveState = false; // set signal to low when accessing chip 
        private const int SpiChipSelectSetupTime = 0;
        private const int SpiChipSelectHoldTime = 0;
        private const bool SpiClockIdleState = false; // set signal to low when not accessing chip
        private const bool SpiClockEdge = true; // sample data on rising edge
        private const int SpiClockRateKHz = 2000;

        private InterruptPort _dataReadyPort;
        private const bool DataReadyGlitchFilter = false;

        private InputPort _activePort;
        private const bool ActivePortGlitchFilter = false;

        private OutputPort _resetPort;
        private const bool ResetInitialState = true;

        private OutputPort _reqPort;
        private const bool ReqInitialState = true;

        private bool _initialized;
        private readonly object _handleDataReadyLock = new object();

        private readonly AciExchangeMessageQueue _aciExchangeMessageQueue = new AciExchangeMessageQueue(8);

        public delegate void EventEventHandler(AciEventBase receivedEvent);
        public event EventEventHandler OnEventReceived = delegate { };

        private bool ReqPort
        {
            set
            {
                if (!_initialized) return;

                _reqPort.Write(value);
            }
        }

        public void Initialize(AciConfig config)
        {
            Debug.Print("Initializing...");
            var spiModule = config.SpiModule;
            var reqPin = config.ReqInputPin;
            var dataReadyPin = config.RdyOutputPin;
            var activePin = config.ActOutputPin;
            var resetPin = config.RstInputPin;
            _mcuSpiBitOrder = config.McuSpiBitOrder;

            _spiConfiguration = new SPI.Configuration(Cpu.Pin.GPIO_NONE, SpiChipSelectActiveState, SpiChipSelectSetupTime,
                SpiChipSelectHoldTime, SpiClockIdleState, SpiClockEdge, SpiClockRateKHz, spiModule);

            _spiPort = new SPI(_spiConfiguration);
            _spiPort.Write(new byte[] {0x00});

            _activePort = new InputPort(activePin, ActivePortGlitchFilter, Port.ResistorMode.PullUp);
            _resetPort = new OutputPort(resetPin, ResetInitialState);
            _reqPort = new OutputPort(reqPin, ReqInitialState);

            _dataReadyPort = new InterruptPort(dataReadyPin, DataReadyGlitchFilter, Port.ResistorMode.PullUp,
                Port.InterruptMode.InterruptEdgeLow);
            _dataReadyPort.OnInterrupt += HandleDataReady;

            _initialized = true;
            Debug.Print("Initialized");

            Reset();
        }

        public bool EnqueueCommand(AciCommandBase command)
        {
            var message = command.Encode();
            var res =  message != null && EnqueueMessage(message);
            if (res)
                Debug.Print("Enqueued command " + command);

            return res;
        }

        public bool EnqueueMessage(byte[] message, bool printDebug = false)
        {
            return EnqueueMessage(new AciExchangeMessage(message), printDebug);
        }

        private bool EnqueueMessage(AciExchangeMessage message, bool printDebug = false)
        {
            if (message?.MessageToSend == null || message.MessageToSend.Length < 2) return false;

            lock (_handleDataReadyLock)
            {
                _aciExchangeMessageQueue.EnqueueExchangeMessage(message);

                if (printDebug)
                    Debug.Print("Enqueued message with opCode " + message.MessageToSend[1]);

                ReqPort = false;
            }

            return true;
        }

        private void Reset()
        {
            _resetPort.Write(!ResetInitialState);
            _resetPort.Write(ResetInitialState);
        }

        private void HandleDataReady(uint data1, uint data2, DateTime time)
        {
            lock (_handleDataReadyLock)
            {
                ReqPort = false;

                var messageQueueCount = _aciExchangeMessageQueue.Count;
                var exchangeMessage = messageQueueCount > 0
                    ? _aciExchangeMessageQueue.DequeueExchangeMessage()
                    : new AciExchangeMessage();

                if (exchangeMessage.MessageToSend.Length > 1)
                    Debug.Print("Dequeued command with opCode " + exchangeMessage.MessageToSend[1]);

                ReadWriteToSpiPort(2, exchangeMessage);

                var numberOfBytesToExchange = Utils.Max(exchangeMessage.MessageLengthToReceive,
                    exchangeMessage.MessageToSend.Length - 2);
                ReadWriteToSpiPort(numberOfBytesToExchange, exchangeMessage);

                var receivedEvent = AciEventFactory.Create(exchangeMessage, messageQueueCount > 0);

                if (receivedEvent != null)
                    Debug.Print("Received event " + receivedEvent);

                OnEventReceived?.Invoke(receivedEvent);

                ReqPort = true;

                if (_aciExchangeMessageQueue.Count > 0)
                    ReqPort = false;
            }
        }

        private void ReadWriteToSpiPort(int numberOfBytes, AciExchangeMessage exchangeMessage)
        {
            if (numberOfBytes <= 0) return;

            var writeBuffer = exchangeMessage.GetNextBytesToSend(numberOfBytes);
            var readBuffer = new byte[writeBuffer.Length];

            if (_mcuSpiBitOrder == BitOrder.Lsb)
            {
                _spiPort.WriteRead(writeBuffer, readBuffer);
            }
            else
            {
                Utils.ReverseBits(writeBuffer);
                _spiPort.WriteRead(writeBuffer, readBuffer);
                Utils.ReverseBits(readBuffer);
            }

            exchangeMessage.SetNextReceivedBytes(readBuffer);
        }
    }
}
